# -*- coding: utf-8 -*-
# 制作本地可执行文件
# pyinstaller -F -i ico.ico -w main.py

# pip install Nuitka
# nuitka --onefile --windows-disable-console --include-qt-plugins=sensible,styles --standalone --show-memory --show-progress --nofollow-imports --enable-plugin=pyqt5 --follow-import-to=PyQt5 --output-dir=out --windows-icon-from-ico=./icon.ico main.py


import time
import sys
import traceback
from time import sleep
from eeprom import Programmer

from main_ui import Ui_MainWindow

# 引入窗口文件
from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QMessageBox, QListWidgetItem
from PyQt5.QtSerialPort import QSerialPortInfo, QSerialPort

from pathlib import Path

# 图标文件
iconB = b'\x00\x00\x01\x00\x01\x00  \x00\x00\x01\x00 \x00\xa8\x10\x00\x00\x16\x00\x00\x00(\x00\x00\x00 \x00\x00\x00@\x00\x00\x00\x01\x00 \x00\x00\x00\x00\x00\x00\x10\x00\x00\xc3\x0e\x00\x00\xc3\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:Od\x00;Of\x00:Of\x00:Of\x00;Of\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00:Oe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pe\x00;Pe\x00;Pf\x00;Pf\x00:Of\x02:Of\x01;Of\x00;Pf\x00;Pf\x02;Oe\x01;Pf\x00;Pf\x00;Pe\x00;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pe\x00;Pe\x04;Pf";PfV:OfH;Pg\t+=P\x00.AU\x00;Pg\x0f;PfP;PfQ;Pf\x1c;Pe\x02;Pe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pf\x00;PfK;Pf\xd0<Qgo<Od\x12\x19\x16\x15\x05\x1d#+\r\x1d")\x0c\x1a\x17\x16\x04=Qf\x18;Qg\x80;Pf\xcc;Pf1;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 /@\x00 0A\x003FZ\x00=Si\x1d8La\xbc-?R\x94 4I\x89!9R\xb5">Y\xc4"=Y\xc2!9P\xb1 4H\x81/BV\xa29Mb\xab@Vm\x0f5I]\x00!1B\x00(7G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 /@\x00 .>\x00\x1e\'2\x07\x1f0AY$<U\xd4%Gi\xff&Py\xff&T\x80\xff\'W\x84\xff\'V\x84\xff&T\x7f\xff%Ow\xff%Ff\xff#;R\xc5\x1f/?G\x1e\x1d \x02!/>\x00!1B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:Pf\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00 0A\x00\x1f.>\x00\x1e)6\x0c!4H\x8d%Gi\xf7(W\x85\xff)\\\x8c\xff)\\\x8c\xff)\\\x8c\xff)[\x8b\xff)[\x8b\xff)\\\x8b\xff)\\\x8c\xff)\\\x8c\xff)V\x82\xff&Fd\xef%7In!&-\x03!0?\x00\x1f0@\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pf\x00;Pf\x05;Pf5:Pf\x06:Pf\x00\x1f0A\x000c\x9a\x00!3Gu\'Lp\xfd,_\x91\xff,`\x92\xff,_\x91\xff,_\x91\xff,`\x91\xff,`\x91\xff-`\x91\xff.a\x92\xff/b\x93\xff0c\x93\xff1d\x94\xff2c\x92\xff+Kk\xf3 1BS"4H\x00\x161=\x00:Pf\x00;Pf\x0c;Pf:;Pf\x02;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pf\x00;Pf\r;Pf\x8f;Pf\x17;Of\x00 0A\x00\x1e*6\x1c%B_\xd8.a\x93\xff/e\x98\xff/d\x98\xff-a\x92\xff/c\x95\xff2g\x9a\xff3g\x99\xff4h\x9a\xff5i\x9b\xff3e\x96\xff5g\x97\xff9l\x9d\xff:l\x9d\xff5d\x90\xff$<U\xb9\x1b"*\n 0@\x00;Pf\x00;Pf";Pf\x98;Pf\x07;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pf\x00;Pf\t;Pf\x9c;Pf#;Pf\x00 0A\x00\x1f,;@)Mp\xf53k\x9f\xff0c\x94\xff$B^\xff3CR\xff-AS\xff(Os\xff9n\xa0\xff7i\x98\xff(Gd\xff4DS\xff1BS\xff,Op\xff@r\xa1\xff<o\xa0\xff\'Ec\xe0\x1d(3\x1d 0A\x00;Pf\x00;Pf9;Pf\xa4;Pf\x06;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00;Pf\x00;Pf\x06;Pf\xa8;Pf=;Pf\x00 0A\x00\x1f-<G+Ot\xf98o\xa4\xff*D]\xff\x98\x98\x98\xff\xe5\xe5\xe4\xff\xd4\xd3\xd2\xffhlo\xff*Kk\xff-D[\xff\x8f\x90\x90\xff\xe0\xdf\xdf\xff\xdc\xdb\xda\xffknq\xff4Wy\xffBv\xa8\xff(Hg\xe1\x1d\'3\x1e 0A\x00;Pf\x00;PfT;Pf\x9b8Ne\x01:Of\x00\x00\x00\x00\x00;Pf\x00;Pf\x00:Pe\x03;Pf\xa7;Pf^;Pf\x00 0A\x00\x1e,:>-Rx\xf45g\x98\xffT\\d\xff\xfa\xf9\xf8\xff\xf2\xf2\xf2\xffqqq\xff\xad\xad\xad\xff?CG\xffsuv\xff\x8d\x8d\x8c\xff\x98\x98\x98\xff\xff\xff\xff\xff\xd6\xd5\xd4\xff6HZ\xffBv\xa9\xff(Gf\xdf\x1c&2\x1d 0A\x00;Pf\x00;Pfz;Pf\x86;Pf\x00:Qd\x00:Rd\x009Oe\x00;Pf\x00;Pf\x00;Pf\x8b;Pf\x87;Pf\x00 0A\x00\x1d(4"+Np\xe26h\x98\xff^dk\xff\xfd\xfc\xfc\xff\xf3\xf3\xf3\xff\x86\x86\x86\xff\xce\xce\xce\xff||{\xff\xb0\xb0\xb0\xff\xa6\xa6\xa6\xff\xa8\xa8\xa8\xff\xff\xff\xff\xff\xde\xdd\xdd\xff:JZ\xffAu\xa7\xff\'C`\xc5\x19\x1e%\r#4F\x009Nd\x01;Pf\xa1;Pfv;Pf\x00;Lh\x00?At\x00;Pf];Pf\x13;Pf\x00;Pfp;Pf\xb1:Oe\x05*;N\x00\x17\x19\x1e\x08&A[\xb5;o\xa1\xff;M_\xff\xd3\xd1\xd0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc\xfc\xfc\xffYYY\xff\x96\x96\x96\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa0\xa0\xa0\xff:Xv\xff<m\x9d\xff#8O\x8eE\x92\xd8\x009Mc\x00;Pf\x0f;Pf\xc4;PfR;Pf\x00;Pf\x1e;PfN;Pf\x97;PfJ;Pf\x00;PfH;Pf\xd1;Pf\x1b;Pf\x00#7L\x00 2D`5`\x8b\xfc=j\x96\xffNW`\xff\xb9\xb8\xb8\xff\xd9\xd8\xd7\xff\x8a\x8b\x8c\xff2H]\xffAP]\xff\xaf\xae\xae\xff\xda\xd9\xd8\xff\x9c\x9c\x9c\xffARb\xffJ}\xaf\xff/Uz\xef\x1f-<= 1B\x00;Pf\x00;Pf-;Pf\xd6;Pf1;Pf\x00;Pfb;Pf\x80;Pf`;Pf\x99:Od\x02;Pf#;Pf\xd9;PfE;Pf\x00\x1f/?\x00\x1b$.\x10\'A[\xb6Bw\xab\xffK{\xa9\xff?[v\xffDZp\xffCd\x85\xff_\x91\xc2\xffX\x86\xb3\xffB]x\xffF\\q\xffEd\x82\xffW\x8a\xbb\xff=m\x9c\xff#:P\x93\x15\x16\x18\x04\x1f.?\x00;Pf\x00;Pf_;Pf\xce;Oe\x13:Pf\x0b;Pf\xa8;PfF;Pf\x1a;Pf\xbb;PfD;Pf\x05;Pf\xbc;Pf\x88;Pf\x00;Pf\x00/BV\x00\x1c*8-*Ih\xd1H~\xb3\xffb\x9a\xd1\xffh\x9c\xd0\xffk\x9f\xd3\xffk\x9e\xd1\xffl\x9f\xd3\xffm\xa1\xd3\xffl\x9f\xd2\xffe\x9c\xd2\xffCt\xa5\xff&@Z\xb9\x1a$1\x181DX\x00;Pf\x00;Oj\x00;Pf\xa3;Pf\xa3<Pg\x00;Pf\\;Pf\xaf;Pf\r;Pf\x00;Pfp;Pf\xb9;Pf\x0f;Pft;Pf\xdf;Pfi;PfK;Pf=>Tj!$5Fp\'C_\xfd>k\x99\xffX\x8e\xc4\xffg\x9d\xd4\xffl\xa2\xd7\xffm\xa2\xd8\xffg\x9d\xd3\xffV\x8b\xbf\xff9d\x8f\xff%>X\xf2\'7HT>Tj$;Pf@;PfL;Pft;Pf\xe5;PfW;Pf\x1c;Pf\xc7;PfW;Pf\x00;Pf\x00;Pe\x14;Pf\xbf;Pf\x87;Pf\x1e;Pf\xa4;Pf\xd9;Pf\xdf;Pf\xe5;Qg\xdd2FZ\xe4(Jk\xfe+Sz\xff-Ps\xff6]\x85\xff;d\x8d\xff;d\x8d\xff5[\x82\xff/Rt\xff-V~\xff(Ge\xfb6J_\xe0;Pf\xdf;Pf\xe4;Pf\xdf;Pf\xd7;Pf\x96:Pf\x1b;Pf\xa0;Pf\xac;Pe\n;Pf\x00;Pe\x00;Pf\x00;PfB;Pf\xdd;Pfj;Pe\x06;Pf\x16;Pf\';PfJ<Qgj3EY\xa2+Mn\xf88p\xa6\xff<p\xa2\xff;j\x98\xff:d\x8e\xff<f\x90\xff@o\x9b\xffEw\xa7\xff9o\xa3\xff)Ea\xf07J^\x96;Pff;PfD;Pf$;Pf\x14:Oe\x08;Pf\x80;Pf\xd6;Pf.;Pf\x00;Pf\x00;Qf\x00;Of\x00=Uf\x00;Pfh;Pf\xe4;Pf\x97;Pf\x88;Pf\xac;Pf\xc6;Pf\xd78La\xea*B[\xfe:p\xa4\xffK\x82\xb7\xffP\x85\xb9\xffR\x87\xba\xffT\x88\xba\xffV\x89\xbb\xffP\x86\xb9\xff6h\x97\xff+?U\xfd;Od\xe6;Pf\xd5;Pf\xc3;Pf\xa8;Pf\x85;Pf\xa0;Pf\xe0;PfP;Pf\x00;Pe\x00:Pe\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x02;Pff;Pf\xc1;Pf\xbb;Pf\x9e;Pf~1CW\x8f(?V\xe9$@\\\xff0X\x80\xffK\x85\xbe\xff]\x92\xc6\xffb\x95\xc6\xffd\x96\xc7\xffa\x94\xc7\xffJ\x82\xb9\xff/Ru\xff)D_\xff.CY\xe04G[\x88;Pf\x81;Pf\xa3;Pf\xbd;Pf\xbd;PfU7K^\x00;Pe\x00;Pf\x00;Pf\x00;Pf\x0c:Of\x0f;Pf\x00;Pf\x00?Of\x00;Pf\t;Pf\x06\x1d,<\x00\x1e-=8#?[\xd7)V\x82\xff*\\\x8c\xff\'Jl\xff6b\x8e\xffP\x8a\xc4\xffa\x99\xd0\xffb\x99\xd0\xffQ\x89\xbf\xff8_\x86\xff0Tw\xff4d\x93\xff2\\\x84\xff%?Y\xca\x1c+:)\x01\r\x18\x00;Pf\x07;Pf\x08;Pe\x00;Pe\x00;Pf\x00;Pf\x0f;Pf\x07;Pf5;Pf\x88;Pf\x0b;Pf\x00:Pf\x00:Pe\x00*<O\x00\x1a\x1e%\x07#;S\xb0-\\\x8a\xff1f\x99\xff2f\x99\xff2d\x95\xff/Ty\xff5X|\xff=f\x8f\xff>f\x8e\xff8Z{\xff7]\x82\xff<m\x9d\xff>p\x9f\xff>p\xa0\xff-Z\x85\xff"7M\x98\x01\x00\x00\x013F[\x00:Pf\x00;Qg\x00;Pf\x00;Pf\x15;Pf\x8a:Pf\x1e;Pe\x07;Pf\x96;Pf~:Oe\x04;Pf\x00;Pf\x00%6H\x00\x19",\x18&C`\xdb4j\x9e\xff:o\xa3\xff;p\xa4\xff=r\xa5\xff>r\xa5\xff=m\x9b\xff<f\x8f\xff>h\x91\xffBq\x9f\xffEw\xa8\xffFx\xa9\xffHy\xa9\xffFy\xa9\xff4h\x9a\xff%A]\xb8\x00\x00\x00\x03*<O\x00;Pf\x00;Pf\x00:Pf\x0b;Pf\x95;Pf\x81:Pg\x01;Pf\x00;Pf%;Pf\xc4;Pf\x83;Pf\x0b;Pf\x00Nh\x7f\x021BTN)Fd\xed8p\xa6\xffBy\xad\xffEz\xae\xffF{\xaf\xffH|\xaf\xffI~\xb0\xffJ\x7f\xb2\xffL\x80\xb2\xffM\x80\xb2\xffN\x81\xb2\xffP\x82\xb3\xffQ\x83\xb3\xffN\x81\xb2\xff8m\xa1\xff)C^\xd77I\\8Tq\x8c\x00;Pf\x00;Pf\x12;Pf\x98;Pf\xb8;Pf\x17;Pf\x00<Tg\x00;Pf\x00;Pf<;Pf\xd3;Pf\xa8;PfK;Pf\x9a:Nc\xe6+D^\xf29p\xa6\xffI\x81\xb7\xffN\x84\xb8\xffO\x85\xb9\xffQ\x86\xb9\xffR\x87\xba\xffS\x88\xba\xffU\x89\xbb\xffV\x8a\xbb\xffW\x8a\xbc\xffY\x8b\xbc\xff[\x8c\xbd\xffO\x85\xb9\xff7k\x9d\xff,BY\xf1;Oe\xe2;Pf\x8b;PfL;Pf\xb8;Pf\xc2;Pf\';Pf\x00;Pf\x00:Of\x00:Pe\x00;Of\x00;Pf1;Pf\xb8;Pf\xec;Pf\xc7<QgZ$6Is3`\x8b\xfdG\x84\xbe\xffV\x8d\xc2\xffY\x8e\xc3\xffZ\x8f\xc3\xff[\x90\xc3\xff\\\x91\xc4\xff]\x91\xc4\xff_\x92\xc5\xff`\x93\xc5\xffb\x95\xc6\xff_\x93\xc5\xffG\x83\xbc\xff0X\x80\xf6%6H`<Qgg;Pf\xd0;Pf\xeb;Pf\xaa;Pf";Pf\x00<Qh\x00;Of\x00\x00\x00\x00\x00;Pe\x00:Rd\x00;Pf\x00;Pf\x11;Pf;;Pf\x143G[\x00\x1a$/\x11\'A[\xb6<q\xa4\xffP\x8d\xc8\xff_\x96\xcc\xffc\x98\xcd\xffd\x99\xcd\xffe\x9a\xce\xffg\x9b\xce\xffh\x9b\xce\xffi\x9c\xcf\xfff\x9a\xce\xffQ\x8d\xc8\xff9k\x9c\xff%<T\x9d\x13\x18\x1f\x075I_\x00;Pf\x1a;Pf:;Pf\x0b;Pf\x00;Qg\x00:Od\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;Qe\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00%6H\x00 /@\x00\x1e*9$(C^\xbb;m\x9e\xffO\x8c\xc9\xff_\x9a\xd4\xffh\x9f\xd6\xffk\xa1\xd6\xffl\xa2\xd7\xffj\xa1\xd7\xffa\x9b\xd4\xffN\x89\xc5\xff8f\x94\xfd\'?X\xa7\x1d(5\x19\x1f.>\x00\x00\x00\x00\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00;Pf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f0A\x00"4G\x00 0@\x00\x1c\'3\x18$8N\x81/Rv\xda<l\x9c\xfaF~\xb5\xffM\x88\xc1\xffM\x87\xc0\xffE{\xb1\xff:h\x96\xf8-No\xd1"5Io\x1a#.\x0e\x1f.>\x00 0B\x00\x1f0A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 0A\x00 0A\x00\x1f.>\x00\x8b\xff\xff\x00\x1b%1% 1Cf%<T\x96(B\\\xb2(B\\\xaf$:Q\x91 0@\\\x1a#.\x1d+Lj\x00\x1f.>\x00 0A\x00 0A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xe0\x07\xff\xff\x80\x01\xff\xff\x80\x01\xff\xff\x80\x01\xff\xfe\x00\x00\x7f\xfe\x00\x00\x7f\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x01\xc0\x00\x00\x03\xfe\x00\x00\x7f\xff\x00\x00\xff'

mainTitle = "EEPROM 编程器 (29-595)"
main_W = 1050
main_H = 560
buffer_size = 128
#校验芯片最大容量 0x80000 = 512k
chip_max_length = 0x80000

class ChipThread(QThread):

    # mode r读取 w写入 c校验 s保存 ff查空
    def __init__(self, port, chipName, startAddress, endAddress, mode='r', file=''):
        super(ChipThread, self).__init__()
        self.port = port
        self.startAddress = startAddress
        self.endAddress = endAddress
        self.mode = mode
        self.file = file
        self.chipName = chipName
        # 读取芯片内容
        self.chipData = []

    # 信号槽
    browserClear_signal = pyqtSignal()  # 清理日志框
    textBrowserAppend_signal = pyqtSignal(str)  # 追加日志
    hexBrowserAppend_signal = pyqtSignal(str)  # 追加hex框
    progressBarChange_signal = pyqtSignal(int)  # 进度条更新

    def appendMsg(self, msg):
        self.textBrowserAppend_signal.emit(msg)

    def appendHexMsg(self, msg):
        self.hexBrowserAppend_signal.emit(msg)

    def progressBarChange(self, value):
        self.progressBarChange_signal.emit(value)

    def run(self):
        self.isHigh = 0
        if self.mode=='w':
            # 写片
            self.write()
        elif self.mode=='c':
            self.check()
        elif self.mode=='ff':
            self.read()
        elif self.mode=='r':
            self.read()
        elif self.mode=='s':
            self.read()
            self.save()
        else:
            # 读片
            self.read()

    def write(self):
        totalCount = 0
        eeprom = Programmer()
        if eeprom.connect(self.port):
            print("连接")
        else:
            print("未连接")
            self.appendMsg("==编程器未连接，或端口被占用==")
            return
        if eeprom.is_open:
            address = self.startAddress
            # datalength = filelength = os.path.getsize(self.file)
            datalength = filelength = Path(self.file).stat().st_size

            # 解锁
            if self.chipName.startswith("28") or self.chipName.startswith("29") or self.chipName.startswith("W29"):
                self.appendMsg("解锁开始")
                eeprom.sendcommand("u", self.chipName)
                unlockline = eeprom.getline()
                if unlockline and unlockline.find("ok")>-1:
                    print("解锁成功,返回:{}".format(unlockline))
                    self.appendMsg("解锁完成")
                else:
                    print("解锁成功,返回:{}".format(unlockline))
                    self.appendMsg("解锁命令调用失败,返回:{}".format(unlockline))
                    return

                if not eeprom.confirmation():
                    self.appendMsg("写入程序返回异常(调用解锁成功后)")
                    return

            sleep(2)

            if self.chipName not in ['29256', '29512']:
                eeprom.sendcommand("c", self.chipName,address, address)
                checkline = eeprom.getline()
                # if checkline and checkline.decode().find("ok") > -1:
                if checkline and checkline.find("ok") > -1:
                    print("检查信息返回:{}".format(checkline))
                    self.appendMsg("检查信息完成")
                else:
                    print("检查信息异常")
                    print(checkline)
                    self.appendMsg("检查信息异常")
                    return

            if not eeprom.confirmation():
                self.appendMsg("写入程序返回异常(调用检查是否支持芯片后)")
                return

            sleep(2)

            # 写入是否出错
            write_error = False
            startTime = time.time()
            with open(self.file, 'rb') as f:
                while datalength > 0 and not write_error:
                    # 读取缓存长度的数据
                    buffer_data = f.read(buffer_size)

                    # 27芯片 而且 数据都是 FF
                    if self.chipName.find("27") > -1 and self.checkFF(buffer_data):
                        print("跳过: {}".format('%05X' % address))
                        address += buffer_size
                        datalength -= buffer_size
                        totalCount += buffer_size
                        self.progressBarChange(int(totalCount * 100 / filelength))
                    else:
                        end = address + buffer_size - 1
                        self.appendMsg('{}-{}'.format(('%05X' % address),('%05X' % end)))

                        # sleep(0.04)
                        eeprom.sendcommand("w", self.chipName, address, end)
                        # 与程序设置延时时间一致后再发送数据
                        # sleep(0.01)
                        eeprom.write(buffer_data)

                        address += buffer_size
                        datalength -= buffer_size
                        totalCount += buffer_size

                        self.progressBarChange(int(totalCount * 100 / filelength))

                        line = eeprom.getline()
                        # 有错就跳出
                        print("line:{}".format(line))

                        if line and (line.find("_")>-1 or len(line) > 10):
                            # 出错返回错误信息及 Ready
                            write_error = True
                            print("写入异常({}) {}".format(len(line),line))
                            self.appendMsg("写入异常 {}".format(line))

                        #
                        # if line:
                        #     # 没有Ready就有问题
                        #     if line.find('Ready') < 0 or len(line) > 10:
                        #         writeHasError = True
                        #         print("写入异常 中断退出")
                        #         self.appendMsg("写入异常 中断退出")
                        #         print(line)
                        #         self.appendMsg(line)
                        #         break
                        # else:
                        #     print("写入没有返回值")

            print("写入完成")
            self.appendMsg("写入完成")

            endTime = time.time()
            self.appendMsg(str("写入耗时: {}".format('%.2f' % (endTime - startTime))))

            eeprom.close()
            sleep(0.5)

            # 没有错误才进行校验
            if not write_error:
                self.mode = 'c'
                self.appendMsg("开始校验")
                self.read()
                self.check()
        else:
            self.appendMsg("编程器连接异常")

    def read(self):
        self.browserClear_signal.emit()
        print("读取开始")
        eeprom = Programmer()
        sleep(1)
        if eeprom.connect(self.port):
            print("连接")
        else:
            print("未连接")
            self.appendMsg("COM连接失败")
        sleep(1)
        if eeprom.is_open:
            # 结束地址
            eeprom.sendcommand("r", self.chipName, self.startAddress, self.endAddress)
            print("发送读取命令: {} {} {} {}".format('r', self.chipName, self.startAddress, self.endAddress))

            startTime = time.time()

            self.chipData = []
            hex16 = []
            str16 = []
            chr16 = []
            notFFcount = 0
            self.address = 0

            while self.address < self.endAddress:

                data1 = eeprom.read(1)
                if not data1:
                    continue
                b = data1[0]
                # 字节加入数组
                self.chipData.append(data1)

                # 查空模式
                if self.mode == 'ff' and b != 0xff:
                    msg = '%05X' % (self.address + len(hex16))
                    msg += ' = '
                    msg += '%02X' % b
                    self.appendMsg(str(msg))
                    notFFcount += 1

                hex16.append(b)
                str16.append('%02X' % b)
                if len(hex16) > 15:
                    for b16 in hex16:
                        if (b16 > 31) and b16 < 127:
                            chr16.append(chr(b16))
                        else:
                            chr16.append(".")

                    hexline = ('%05X' % self.address) + ": "
                    hexline += " ".join(str16) + "  "
                    hexline += ''.join(chr16)

                    # 数据缓存到8k
                    self.appendHexMsg(hexline)

                    hex16 = []
                    str16 = []
                    chr16 = []
                    self.address += 16
                    if self.address % 0x2000 == 0 and self.address != (self.endAddress + 1):
                        # 按照8k数据显示横线
                        self.appendHexMsg("-" * 53)
                    self.progressBarChange(int(self.address * 100 / self.endAddress))

            # self.appendHexMsg('\n'.join(hex8K))
            endTime = time.time()

            # 查空结果
            if self.mode == 'ff':
                if notFFcount>0:
                    self.appendMsg("查空结果: {} 个非 FF 数据".format(notFFcount))
                else:
                    self.appendMsg("查空正常")

            self.appendMsg(str("读取耗时: {}".format('%.2f' % (endTime - startTime))))
        else:
            self.appendMsg("编程器连接异常")

    def save(self):
        if self.chipData:
            try:
                print("数据长度:{}".format(len(self.chipData)))
                print("写入文件:{}".format(self.file))
                with open(self.file, 'wb') as f:
                    for b in self.chipData:
                        f.write(b)
                        f.flush()
                self.appendMsg("保存文件完成")
            except Exception as e:
                print("错误")
                print(e)
                traceback.print_exc()
                # QMessageBox.warning(None, "错误", "文件写入失败", QMessageBox.Yes)
        else:
            QMessageBox.warning(None, "错误", "数据异常", QMessageBox.Yes)


    # 校验数据一致性
    def check(self):
        data = self.chipData
        self.appendMsg("校验数据")
        if not self.file:
            self.appendMsg("没有选择校验文件")
            return

        if len(data) == 0:
            # self.appendMsg("读取数据长度为0")
            # return
            self.read()
            data = self.chipData

        dataSize = Path(self.file).stat().st_size
        if dataSize != len(data):
            self.appendMsg("[异常]读取数据长度与文件不一致")
            self.appendMsg("文件长度: {}".format(dataSize))
            self.appendMsg("数据长度: {}".format(len(data)))
            return

        isSame = True
        errorCount = 0
        buffer = []
        try:
            with open(self.file, 'rb') as f:
                count = 0
                for b in data:
                    readb = f.read(1)
                    if readb != b:
                        isSame = False
                        errorCount += 1

                        # self.appendMsg("{}: {} {}".format(
                        #     '%04X' % count,
                        #     '%02s' % hex(readb[0])[2:].upper(),
                        #     '%02s' % hex(b[0])[2:].upper()))

                        buffer.append("{}: {} {}".format(
                            '%05X' % count,
                            '%02s' % hex(readb[0])[2:].upper(),
                            '   %02s' % hex(b[0])[2:].upper()))
                        # if len(buffer)>9:
                        #     self.appendMsg("\n".join(buffer))
                        #     buffer = []
                    count += 1
                    if count % 64 == 0:
                        self.progressBarChange(int(count * 100 / len(data)))
        except Exception as e:
            print("错误")
            print(e)
            traceback.print_exc()
        self.appendMsg("校验结果: {}".format("一致" if isSame else "不一致"))
        if not isSame:
            self.appendMsg("地址   文件 ROM")
            self.appendMsg('\n'.join(buffer))
            precent = (dataSize - errorCount) * 100 / dataSize
            print("precent: {}".format(precent))
            if precent > 99.9:
                self.appendMsg("一致率 : {}%".format('99.99'))
            else:
                self.appendMsg("一致率 : {}%".format('%.2f' % precent))
        self.appendMsg("校验结束")


class MQt(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self):
        super(MQt, self).__init__()
        self.setupUi(self)

        # 固定宽高
        self.setFixedSize(main_W, main_H)

        # 设置图标
        if Path('icon.ico').exists():
            self.setWindowIcon(QIcon('icon.ico'))
        else:
            icon = QPixmap()
            icon.loadFromData(iconB)
            self.setWindowIcon(QIcon(icon))

        self.setWindowTitle(mainTitle)

        # 开串口连接
        self.com = QSerialPort()

        self.updateButton.setEnabled(True)
        # 获取串口连接列表
        self.reload_ports()
        # 刷新
        self.updateButton.clicked.connect(self.reload_ports)
        # 列表项选中
        self.portList.itemClicked.connect(self.portList_itemClicked)
        # 点击连接按钮
        self.connectButton.clicked.connect(self.connectButton_clicked)
        self.readButton.clicked.connect(self.readButton_clicked)

        # 清除日志和hex日志
        self.clearButton.clicked.connect(lambda: (self.textBrowser.clear() or self.hexBrowser.clear()))

        # 文件 -> rom
        self.writeChipButton.clicked.connect(self.writeChipButton_clicked)
        # rom -> 文件
        self.writeFileButton.clicked.connect(self.writeFileButton_clicked)
        # 查文件和芯片内容
        self.checkFileButton.clicked.connect(self.checkFileButton_clicked)

        # 查ff
        self.verifyChipButton.clicked.connect(self.verifyChipButton_clicked)
        # 擦除
        self.eraserChipButton.clicked.connect(self.eraserChipButton_clicked)
        # 芯片信息
        self.chipInfoButton.clicked.connect(self.chipInfoButton_clicked)

        self.hexBrowser.setFontPointSize(8)

        self.mode = 0
        self.address = 0
        self.startAddress = 0
        self.selectFile = ''
        self.chipName = ''
        # 是否通过连接测试
        self.tested = False

        self.radios = [
            # {'chip': self.radio2864, 'name': '2864', 'length': 0x1fff, 'VPP': 0},
            # {'chip': self.radio28256, 'name': '28256', 'length': 0x7fff, 'VPP': 0},
            {'chip': self.radio29010, 'name': '29010', 'length': 0x1ffff, 'VPP': 0},
            {'chip': self.radio29020, 'name': '29020', 'length': 0x3ffff, 'VPP': 0},
            {'chip': self.radio29040, 'name': '29040', 'length': 0x7ffff, 'VPP': 0},

            {'chip': self.radioW29256, 'name': '29011', 'length':  0x7fff, 'VPP': 0},
            {'chip': self.radioW29512, 'name': '29011', 'length':  0xffff, 'VPP': 0},
            {'chip': self.radioW29010, 'name': '29011', 'length': 0x1ffff, 'VPP': 0},
            {'chip': self.radioW29020, 'name': '29021', 'length': 0x3ffff, 'VPP': 0},
            {'chip': self.radioW29040, 'name': '29041', 'length': 0x7ffff, 'VPP': 0},
        ]

        # 循环增加点击事件
        for cp in self.radios:
            cp['chip'].toggled.connect(self.changeChip)

        # self.radio29010.click()

        for cp in self.radios:
            cp['chip'].setDisabled(True)


    # 刷新com口列表
    def reload_ports(self):
        self.portList.clear()
        self.connectButton.setEnabled(False)
        ports = QSerialPortInfo.availablePorts()
        print(ports)
        for port in ports:
            qitem = QListWidgetItem()
            if port.isBusy():
                qitem.setText(port.portName() + " (Busy)")
                qitem.setFlags(qitem.flags() & Qt.ItemIsUserCheckable)
            else:
                qitem.setText("{} {}".format(port.portName(), port.description()))
                qitem.setData(Qt.UserRole, port.portName())
                print(port.description())

            self.portList.addItem(qitem)

    # 选择com列表中的数据
    def portList_itemClicked(self):
        item = self.portList.currentItem()
        if item and item.flags():
            self.port = self.portList.currentItem().data(Qt.UserRole)
            self.connectButton.setEnabled(True)
            self.updateButton.setEnabled(True)

    def enableChip(self):
        self.verifyChipButton.setEnabled(True)
        self.eraserChipButton.setEnabled(True)
        self.chipInfoButton.setEnabled(True)
        # 读取按钮
        self.readButton.setEnabled(True)
        # 校验文件按钮
        self.checkFileButton.setEnabled(True)
        # 开启导入导出按钮
        self.writeChipButton.setEnabled(True)
        self.writeFileButton.setEnabled(True)

    def disableChip(self):
        self.verifyChipButton.setEnabled(False)
        self.eraserChipButton.setEnabled(False)
        self.chipInfoButton.setEnabled(False)
        # 读取按钮
        self.readButton.setEnabled(False)
        # 校验文件按钮
        self.checkFileButton.setEnabled(False)
        # 开启导入导出按钮
        self.writeChipButton.setEnabled(False)
        self.writeFileButton.setEnabled(False)

    # 点击connect连接按钮
    def connectButton_clicked(self):
        print("选择端口:{}".format(self.port))
        eeprom = Programmer()
        testRe = eeprom.testConnect(self.port)
        if testRe > 0:
            self.appendMsg("测试连接 {} 正常".format(self.port))
            self.enableChip()
            self.tested = True
            for cp in self.radios:
                cp['chip'].setDisabled(False)
            self.radio29010.click()
        else:
            # 错误返回小于0的值
            self.disableChip()
            self.tested = False
            for cp in self.radios:
                cp['chip'].setDisabled(True)
            if testRe == -9999:
                self.appendMsg("客户端版本与编程器不匹配")
                QMessageBox.warning(None, "错误", "客户端版本与编程器不匹配", QMessageBox.Yes)
            else:
                QMessageBox.warning(None, "错误", "连接COM失败,或未准备好", QMessageBox.Yes)

    def doChipThread(self):
        readChipThread = ChipThread(self.port, self.chipName, self.startAddress, self.endAddress, self.mode, self.selectFile)
        # 连接信号槽
        readChipThread.browserClear_signal.connect(self.browserClear)
        readChipThread.hexBrowserAppend_signal.connect(self.hexBrowserAppend)
        readChipThread.textBrowserAppend_signal.connect(self.appendMsg)
        readChipThread.progressBarChange_signal.connect(self.progressBarChange)
        readChipThread.start()
        readChipThread.exec()

    def readButton_clicked(self):
        self.init()

        print("readButton_clicked")
        self.hexBrowser.clear();

        self.mode='r'
        self.appendMsg("读取开始")
        self.doChipThread()

    # 写入芯片 读文件
    def writeChipButton_clicked(self):
        self.init()

        fileName, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选择bin文件", str(Path.cwd()),
                                                                   "Bin Files(*.bin);;All Files(*)")

        if not fileName:
            self.appendMsg("未选择文件")
            return

        fileSize = Path(fileName).stat().st_size
        if not Path(fileName).is_file() or fileSize > chip_max_length:
            self.appendMsg("[异常]文件异常或体积大于芯片容量")
            self.appendMsg("文件大小:{}".format(fileSize))
            self.appendMsg("芯片容量:{}".format(self.chipLength))
            return

        self.selectFile = fileName
        self.appendMsg("读取文件: {}".format(fileName))

        # 设置写入模式
        self.mode='w'
        self.appendMsg("写入开始")
        self.doChipThread()

    # 读芯片 写文件
    def writeFileButton_clicked(self):
        self.init()
        fileName, fileType = QtWidgets.QFileDialog.getSaveFileName(self, "选择bin文件", str(Path.cwd()), "Bin Files(*.bin)")

        if not fileName:
            self.appendMsg("未选择文件")
            return

        self.selectFile = fileName
        self.appendMsg("输出文件: {}".format(fileName))

        # 读取并保存文件
        self.mode = 's'
        self.appendMsg("读取开始")
        self.doChipThread()

    # 校验内容
    def checkFileButton_clicked(self):
        self.init()

        fileName, fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选择bin文件", str(Path.cwd()),
                                                                   "Bin Files(*.bin);;All Files(*)")
        if not fileName:
            self.appendMsg("未选择文件")
            return

        fileSize = Path(fileName).stat().st_size
        if not Path(fileName).exists() or not Path(fileName).is_file() or fileSize > chip_max_length:
            self.appendMsg("[异常]文件异常或文件体积大于芯片容量")
            self.appendMsg("文件大小:{}".format(fileSize))
            self.appendMsg("芯片容量:{}".format(self.chipLength))
            return

        if fileSize != self.chipLength:
            self.appendMsg("[异常]文件大小与芯片不一致")
            self.appendMsg("文件大小:{}".format(fileSize))
            self.appendMsg("芯片容量:{}".format(self.chipLength))
            return

        self.selectFile = fileName

        # 校验
        self.mode = 'c'
        self.appendMsg("读取并校验开始")
        self.doChipThread()

    # 查空 非ff
    def verifyChipButton_clicked(self):
        self.init()
        self.appendMsg("查空开始")
        self.mode = 'ff'
        self.doChipThread()

    # 擦0
    def eraserChipButton_clicked(self):
        self.init()

        reply = QMessageBox.question(self, '消息', '是否确认擦除?\n需要等待10秒后再进行其他操作', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply != QMessageBox.Yes:
            print("取消擦除")
            return
        else:
            print("擦除开始")
            self.appendMsg("擦除开始")
            self.appendMsg("请5秒后在进行操作")
        eeprom = Programmer()
        if eeprom.connect(self.port):
            print("连接")
        else:
            print("未连接")
            self.appendMsg("==编程器未连接，或端口被占用==")
            return
        if eeprom.is_open:
            print("准备擦除")
            self.appendMsg("准备擦除")
            eeprom.sendcommand("e", self.chipName)
            if self.chipName.startswith("27"):
                sleep(1)
                print("擦除1次")
                self.appendMsg("擦除1次,再次擦除以保证擦除完整")
                eeprom.sendcommand("e", self.chipName)
                sleep(1)
            print("擦除完成")
            self.appendMsg("擦除完成,请稍等5秒后操作")
            w_re = eeprom.getline()
            if w_re:
                self.appendMsg("{}".format(w_re))

            self.appendMsg("擦除后建议使用查空进行检查")
        else:
            self.appendMsg("编程器连接异常")

    # 读取芯片信息
    def chipInfoButton_clicked(self):
        self.init()
        self.appendMsg("未设计此功能")
        # print("读取芯片信息开始")
        # self.appendMsg("读取芯片信息开始")
        # eeprom = Programmer()
        # if eeprom.connect(self.port):
        #     print("连接")
        # else:
        #     print("未连接")
        #     self.appendMsg("==编程器未连接，或端口被占用==")
        #     return
        # if eeprom.is_open:
        #
        #     self.address = 0
        #     eeprom.sendcommand("s",self.chipName)
        #     print("读取芯片信息完成")
        #     self.appendMsg("读取芯片信息完成")
        #     data = eeprom.read(2)
        #     if data:
        #         sips = []
        #         for d in data:
        #             sips.append(('%02X' % d))
        #         self.appendMsg("CHIP: {}".format(' '.join(sips)))
        # else:
        #     self.appendMsg("编程器连接异常")

    # ---------------------------------------

    def changeChip(self):
        for cp in self.radios:
            if cp['chip'].isChecked():
                self.chip = cp
                self.chipLength = cp['length']+1
                self.chipName = cp['name']
                self.endAddress = cp['length']


        if self.tested and self.chip['VPP']==0:
            # 启用
            self.writeChipButton.setDisabled(False)
            self.eraserChipButton.setDisabled(False)
        else:
            # 禁用
            self.writeChipButton.setDisabled(True)
            self.eraserChipButton.setDisabled(True)
        print("选择了{}".format(self.chip))

    def init(self):
        self.selectFile = ''
        self.mode = 'r'
        self.isHigh = 0
        self.address = 0

    # 16进制字符串转数值
    def strToInt(self, data):
        return (self.hexFromByte(data[0]) * 65536) + \
               (self.hexFromByte(data[1]) * 4096) + \
               (self.hexFromByte(data[2]) * 256) + \
               (self.hexFromByte(data[3]) * 16) + \
               (self.hexFromByte(data[4]))

    # byte 转 hex
    def hexFromByte(self, txt):
        for i in range(len(txt)):
            if ord('0') <= ord(txt[i]) <= ord('9'):
                return ord(txt[i]) - ord('0')
            elif ord('a') <= ord(txt[i]) <= ord('f'):
                return ord(txt[i]) - ord('a') + 10
            elif ord('A') <= ord(txt[i]) <= ord('F'):
                return ord(txt[i]) - ord('A') + 10
            else:
                return 0

    # 进度条
    def progressBarChange(self, value):
        if not self.progressBar.isEnabled():
            self.progressBar.setEnabled(True)
        self.progressBar.setValue(int(value))

    # 清空
    def browserClear(self):
        self.hexBrowser.clear()

    # 追加
    def hexBrowserAppend(self, msg):
        self.hexBrowser.append(msg)

    # 消息框内容
    def appendMsg(self, msg):
        self.textBrowser.append(msg)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    win = MQt()
    win.show()
    sys.exit(app.exec_())
